package cn.song.util.easyexcel;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.song.model.DownloadDto;
import cn.song.model.R;
import cn.song.util.*;
import cn.song.util.easyexcel.model.BatchCount;
import cn.song.util.easyexcel.model.NoModelWriteData;
import cn.song.util.easyexcel.model.SimpleWriteData;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.github.pagehelper.PageHelper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.poi.ss.SpreadsheetVersion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static cn.hutool.json.JSONUtil.toJsonStr;


/**
 * EasyExcel工具类
 *
 * @author SongRenShuo
 * @date 2023/12/25
 */
@Slf4j
@Component
public class EasyExcelUtils {

    /**
     * 每次查询5000条数据
     */
    public static final int QUERY_COUNT = 5000;
    /**
     * 每个sheet保存10万条数据
     */
    public static final int PER_SHEET_COUNT = 100000;

    public EasyExcelUtils() {
        // 重置单元格最大文本长度
        resetCellMaxTextLength();
    }

    /**
     * 没有模式写入
     * 不创建对象的导出
     *
     * @param data                   数据
     * @param response               响应
     * @param customCellWriteHandler 自定义单元格写入处理程序 - 可为null
     * @throws IOException ioexception
     * @author SongRenShuo
     */
    @Async
    public void noModleWrite(@RequestBody NoModelWriteData data, HttpServletResponse response, CellWriteHandler customCellWriteHandler) throws IOException {
        // 这里注意 使用swagger 会导致各种问题，请直接用浏览器或者用postman
        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode(data.getFileName(), "UTF-8");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
            if (customCellWriteHandler == null) {
                // 这里需要设置不关闭流
                EasyExcel.write(response.getOutputStream())
                        .head(head(data.getHeadMap()))
                        .sheet(data.getFileName())
                        .doWrite(dataList(data.getDataList(), data.getDataStrMap()));
            } else {
                // 这里需要设置不关闭流
                EasyExcel.write(response.getOutputStream())
                        .head(head(data.getHeadMap()))
                        .sheet(data.getFileName())
                        // 自定义颜色样式
                        .registerWriteHandler(customCellWriteHandler)
                        .doWrite(dataList(data.getDataList(), data.getDataStrMap()));
            }
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = new HashMap<>();
            map.put("status", "failure");
            map.put("message", "下载文件失败" + e.getMessage());
            response.getWriter().println(JSONUtil.toJsonStr(map));
        }
    }


    /**
     * 创建对象的导出
     *
     * @param data                   数据
     * @param clazz                  clazz
     * @param response               响应
     * @param customCellWriteHandler 自定义单元格写入处理程序 - 可为null
     * @throws IOException ioexception
     * @author SongRenShuo
     */
    @Async
    public <T> void simpleWrite(@RequestBody SimpleWriteData data, Class<T> clazz, HttpServletResponse response, CellWriteHandler customCellWriteHandler) throws IOException {
        // 这里注意 有反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode(data.getFileName(), "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");

        if (customCellWriteHandler == null) {
            EasyExcel.write(response.getOutputStream(), clazz)
                    .sheet(data.getFileName())
                    .doWrite(data.getDataList());
        } else {
            EasyExcel.write(response.getOutputStream(), clazz)
                    .sheet(data.getFileName())
                    // 自定义颜色样式
                    .registerWriteHandler(customCellWriteHandler)
                    .doWrite(data.getDataList());
        }

    }


    /**
     * 文件下载 - 调用示例
     *
     * @param entity    查询实体
     * @param startTime 开始时间 - 默认当天00:00:00
     * @param endTime   结束时间 - 默认当天23:59:59
     * @author SongRenShuo
     */
//    @RequestMapping("download")
//    public void download(Object entity, String startTime, String endTime, HttpServletResponse response) throws IOException {
//        log.info("文件下载 - 调用示例");
//
//        // 如果为空则默认获取当天时间
//        Date[] isNullDate = getIsNullDate(startTime, endTime);
//        Date parseDate = isNullDate[0];
//        Date endDate = isNullDate[1];
//        // 获取相差天数
//        long between = getBetween(parseDate, endDate, "day");
//        System.out.println("文件下载 - 其他订单 相差天数 = " + between);
//        if (between > 31) {
//            response.setCharacterEncoding("UTF-8");
//            response.setContentType("application/json;charset=utf-8");
//            response.getWriter().println(JSONUtil.toJsonStr(failed("时间区间过长，不允许超过30天")));
//        } else {
//            // 需要导出的数据
//            ArrayList<Object> data = new ArrayList<>();
//
//            // 导出
//            SimpleWriteData d = new SimpleWriteData();
//            d.setFileName("导出示例");
//            d.setDataList(data);
//            EasyExcelUtils easyExcelUtils = new EasyExcelUtils();
//            easyExcelUtils.simpleWrite(d, Object.class, response, null);
//        }
//
//    }


    /**
     * 文件上传 - 调用示例
     *
     * @param file 文件
     * @return 上传成功/失败
     * @author SongRenShuo
     */
//    @PostMapping("upload")
    public void upload(MultipartFile file) throws IOException {
        // if (ObjUtil.isNull(file)) {
        //     return failed("文件不能为空");
        // }
        //    logUtil.getInsertLog(6, 13, "文件上传 - ");
        //    EasyExcelUtils excelSuperUtil = new EasyExcelUtils();
        //    excelSuperUtil.upload(file,service, entity.class);
        //    excelSuperUtil.upload(file,实体类对应服务层接口, 实体类.class);
    }


    /**
     * 文件上传
     *
     * @param filePath      文件路径
     * @param entityClass   实体类
     * @param readListener 监听器
     * @param message       模块
     * @return {@link R }
     * @date 2024/03/20
     * @author SongRenShuo
     */
    public static R<String> upload(String filePath, Class<?> entityClass, ReadListener<?> readListener, String message) {

        try {
            EasyExcel.read(filePath, entityClass, readListener)
                    .sheet().doRead();
        } catch (Exception e) {
            log.error("{} 文件{}上传异常:{}", message, filePath, e.getMessage(), e);
            throw new RuntimeException("上传失败：" + e.getMessage());
        }
        return R.success("上传成功");
    }

    /**
     * 设置表头
     *
     * @param headMap 表头数组
     * @return {@link List}<{@link List}<{@link String}>>
     */
    public static List<List<String>> head(String[] headMap) {
        List<List<String>> list = new ArrayList<>();

        for (String head : headMap) {
            List<String> headList = new ArrayList<>();
            headList.add(head);
            list.add(headList);
        }
        return list;
    }


    /**
     * 设置导出的数据内容
     *
     * @param dataList   数据列表
     * @param dataStrMap 数据 str 映射
     * @return {@link List}<{@link List}<{@link Object}>>
     */
    public static List<List<Object>> dataList(List<Map<String, Object>> dataList, String[] dataStrMap) {
        List<List<Object>> list = new ArrayList<>();
        for (Map<String, Object> map : dataList) {
            List<Object> data = new ArrayList<>();
            for (String s : dataStrMap) {
                data.add(map.get(s));
            }
            list.add(data);
        }
        return list;
    }


    /**
     * 检查对象的所有字段或Map中的值是否为null。
     *
     * @param object       对象或Map实例
     * @param ignoreFields 需要忽略检查的字段名或键名数组
     * @return 包含所有为null的字段名或键名的列表
     * @author SongRenShuo
     * @date 2024/12/14
     */
    public static List<String> checkObjOrMapAllEntriesIsNull(Object object, String... ignoreFields) {
        // 判断传入的对象是否是Map类型，如果是，则调用专门处理Map的方法；否则，处理普通对象
        if (object instanceof Map) {
            return checkMapAllValuesIsNull((Map<?, ?>) object, ignoreFields);
        } else {
            return checkObjAllFieldsIsNull(object, ignoreFields);
        }
    }

    /**
     * 检查普通JavaBean对象的所有字段是否为null，并忽略指定的字段。
     *
     * @param bean         JavaBean对象实例
     * @param ignoreFields 需要忽略检查的字段名数组
     * @return 包含所有为null的字段名的列表
     */
    private static List<String> checkObjAllFieldsIsNull(Object bean, String... ignoreFields) {
        // 将需要忽略的字段名转换为Map，方便快速查找
        Map<String, Boolean> ignoreFieldMap = Arrays.stream(ignoreFields)
                .collect(Collectors.toMap(String::new, k -> true));

        List<String> nullFields = new ArrayList<>();
        Field[] fields = bean.getClass().getDeclaredFields();

        for (Field field : fields) {
            // 设置字段可访问，以便反射获取私有字段的值
            field.setAccessible(true);
            try {
                String fieldName = field.getName();
                // 如果当前字段在忽略列表中，则跳过检查
                if (ignoreFieldMap.getOrDefault(fieldName, false)) {
                    log.info("检查类所有字段是否为null 忽略字段:{}", fieldName);
                    continue;
                }
                // 如果字段值为null，则记录该字段名
                if (field.get(bean) == null) {
                    nullFields.add(fieldName);
                }
            } catch (IllegalAccessException e) {
                log.error("检查类所有字段是否为null异常:{}", e.getMessage(), e);
            }
        }
        return nullFields;
    }

    /**
     * 检查Map中的所有值是否为null，并忽略指定的键。
     *
     * @param map        要检查的Map实例
     * @param ignoreKeys 需要忽略检查的键名数组
     * @return 包含所有为null的键名的列表
     */
    private static List<String> checkMapAllValuesIsNull(Map<?, ?> map, String... ignoreKeys) {
        // 将需要忽略的键名转换为Set，方便快速查找
        Set<String> ignoreKeySet = new HashSet<>(Arrays.asList(ignoreKeys));
        List<String> nullKeys = new ArrayList<>();

        for (Map.Entry<?, ?> entry : map.entrySet()) {
            String key = String.valueOf(entry.getKey());
            // 如果当前键在忽略列表中，则跳过检查
            if (ignoreKeySet.contains(key)) {
                log.info("检查Map所有值是否为null 忽略键:{}", key);
                continue;
            }
            // 如果值为null，则记录该键名
            if (entry.getValue() == null) {
                nullKeys.add(key);
            }
        }
        return nullKeys;
    }

    /**
     * 检查表头是否与实体类字段一致
     * <p>
     *   通过@ExcelProperty注解反射获取实体类字段
     * @param headMap 表头数组
     * @param modelClass 模型类
     * @param whetherItIsStrictlyVerified  是否严格校验(不允许存在多余表头)
     * @return true 表头一致，false 表头不一致
     */
    public static boolean checkHead(String[] headMap, Class<?> modelClass, boolean whetherItIsStrictlyVerified) {
        HeadCheckResult result = checkHeadWithDetails(headMap, modelClass, whetherItIsStrictlyVerified);
        return result.isConsistent();
    }

    /**
     * 检查表头是否与实体类字段一致，并返回具体的错误信息
     * <p>
     * 通过@ExcelProperty注解反射获取实体类字段
     * <pre>{@code
     *     @Override
     *         public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
     *             // 获取表头信息
     *             List<String> headList = new ArrayList<>();
     *             for (int i = 0; i < headMap.size(); i++) {
     *                 ReadCellData<?> cellData = headMap.get(i);
     *                 if (cellData != null && cellData.getStringValue() != null) {
     *                     headList.add(cellData.getStringValue());
     *                 }
     *             }
     *
     *             String[] heads = headList.toArray(new String[0]);
     *             // 执行表头校验
     *             this.headCheckResult = EasyExcelUtils.checkHeadWithDetails(heads, User.class, strictMode);
     *             // 这里校验不通过可以直接抛异常,终止读取,异常由调用方处理
     *             Assert.isTrue(this.headCheckResult.isConsistent(), "表头校验未通过,请检查模板是否被更改: " + headCheckResult);
     *
     *         }
     * }</pre>
     * @param headMap 表头数组，表示Excel文件中的实际表头
     * @param modelClass 模型类，用于获取期望的表头信息
     * @param whetherItIsStrictlyVerified 是否严格校验(不允许存在多余表头)
     * @return HeadCheckResult 检查结果，包含是否一致和具体错误信息
     */
    public static HeadCheckResult checkHeadWithDetails(String[] headMap, Class<?> modelClass, boolean whetherItIsStrictlyVerified) {
        try {
            if (headMap == null || modelClass == null) {
                return new HeadCheckResult(false, null, null);
            }

            // 获取实体类中所有带有@ExcelProperty注解的字段对应的表头
            Set<String> expectedHeads = Arrays.stream(modelClass.getDeclaredFields())
                    .map(field -> field.getAnnotation(ExcelProperty.class))
                    .filter(Objects::nonNull)
                    .filter(excelProperty -> excelProperty.value() != null && excelProperty.value().length > 0)
                    .map(excelProperty -> excelProperty.value()[0])
                    .collect(Collectors.toSet());

            // 将输入的表头转换为Set便于比较
            Set<String> actualHeads = new HashSet<>(Arrays.asList(headMap));

            // 找出缺少的表头列：存在于期望表头但在实际表头中缺失的项
            Set<String> missingHeads = expectedHeads.stream()
                    .filter(head -> !actualHeads.contains(head))
                    .collect(Collectors.toSet());

            Set<String> extraHeads = Collections.emptySet();

            // 如果是严格校验，找出多余的表头列：存在于实际表头但不在期望表头中的项
            if (whetherItIsStrictlyVerified) {
                extraHeads = actualHeads.stream()
                        .filter(head -> !expectedHeads.contains(head))
                        .collect(Collectors.toSet());
            }

            // 判断表头是否一致：只有当缺少和多余的表头都为空时才认为一致
            boolean isConsistent = missingHeads.isEmpty() && extraHeads.isEmpty();

            return new HeadCheckResult(isConsistent, missingHeads, extraHeads);
        } catch (SecurityException e) {
            log.error("检查表头是否与实体类字段一致 异常 表头数组:{} 模型类:{} 异常原因:{}",
                    headMap, modelClass != null ? modelClass.getName() : null, e.getMessage(), e);
            return new HeadCheckResult(Boolean.FALSE, null, null);
        }
    }

    /**
     * 表头检查结果类，封装表头校验的详细信息
     */
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class HeadCheckResult {
        /** 表头是否一致的标识
         */
        private boolean consistent;
        /** 缺少的表头列集合
         */
        private Set<String> missingHeads;
        /** 多余的表头列集合
         */
        private Set<String> extraHeads;

        /**
         * 重写toString方法，提供格式化的错误信息输出
         *
         * @return String 格式化的表头校验结果字符串
         */
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("表头校验结果: ").append(consistent ? "一致" : "不一致");
            if (!missingHeads.isEmpty()) {
                sb.append(", 缺少表头列: ").append(String.join(", ", missingHeads));
            }
            if (!extraHeads.isEmpty()) {
                sb.append(", 多余表头列: ").append(String.join(", ", extraHeads));
            }
            return sb.toString();
        }
    }


    @Autowired
    private PlatformTransactionManager transactionManager;
    @Autowired
    private DownloadUtil downloadUtil;
    @Value("${baseUrl}")
    private String baseUrl;
    @Autowired
    private RedisUtil redisUtil;


    /**
     * 分批导出
     * <p>
     * 调用示例：
     * <pre>
     *   public R download(QueryDto dto, DownloadDto downloadDto) {
     *
     *         String fileName = getFileNameDate("订单_") + ".xlsx";
     *
     *         return easyExcelUtils.exportInBatches(fileName, FilePath.DOWN_ORDER, FilePath.DOWN_ORDER_XD,
     *                 OrderBean.class,
     *                 () -> orderMapper.selectAll(dto),
     *                 orderMapper.selectCount(dto),
     *                 downloadDto, dto);
     *     }
     *
     * @param fileName     文件名
     * @param filePath     文件路径
     * @param relativePath 相对路径
     * @param modelClass   模型类
     * @param listSupplier 数据订处理器
     * @param totalCount   总计数
     * @param downloadDto  下载 DTO
     * @param ignoreParamList 忽略的字段
     * @param objects      参数信息
     * @return {@link R }
     * @date 2024/09/06
     * @author SongRenShuo
     */
    public R exportInBatches(String fileName, String filePath, String relativePath,
                             Class<?> modelClass, Supplier<List<?>> listSupplier,
                             Long totalCount, DownloadDto downloadDto, List<String> ignoreParamList, Object... objects) {
        return exportInBatches(fileName, filePath, relativePath, modelClass, listSupplier, null, totalCount, downloadDto, ignoreParamList, objects);
    }

    /**
     * 分批导出 - 年份分表
     * <p>
     * 调用示例：
     * <pre>
     *   public R download(QueryDto dto, DownloadDto downloadDto) {
     *
     *         String fileName = getFileNameDate("订单_") + ".xlsx";
     *
     *         return easyExcelUtils.exportInBatches(fileName, FilePath.DOWN_ORDER, FilePath.DOWN_ORDER_XD,
     *                 OrderBean.class,
     *                 pageUtils ->  downOrder(dto,pageUtils),
     *                 orderMapper.selectCount(dto),
     *                 downloadDto, dto);
     *     }
     *
     * @param fileName     文件名
     * @param filePath     文件路径
     * @param relativePath 相对路径
     * @param modelClass   模型类
     * @param listFunction 数据订处理器
     * @param totalCount   总计数
     * @param downloadDto  下载 DTO
     * @param ignoreParamList 忽略的字段
     * @param objects      参数信息
     * @return {@link R }
     * @date 2024/09/06
     * @author SongRenShuo
     */
    public R exportInBatches(String fileName, String filePath, String relativePath,
                             Class<?> modelClass, Function<PageUtils<?>, List<?>> listFunction,
                             Long totalCount, DownloadDto downloadDto, List<String> ignoreParamList, Object... objects) {
        return exportInBatches(fileName, filePath, relativePath, modelClass, null, listFunction, totalCount, downloadDto, ignoreParamList, objects);
    }


    /**
     * 分批导出 - 核心实现
     *
     * @param fileName     文件名
     * @param filePath     文件路径
     * @param relativePath 相对路径
     * @param modelClass   模型类
     * @param listSupplier 数据查询器
     * @param listFunction 数据查询处理器
     * @param totalCount   总计数
     * @param downloadDto  下载 DTO
     * @param ignoreParamList 忽略的字段
     * @param objects      参数信息
     * @return {@link R }
     * @date 2024/09/06
     * @author SongRenShuo
     */
    private R exportInBatches(String fileName, String filePath, String relativePath,
                              Class<?> modelClass, Supplier<List<?>> listSupplier, Function<PageUtils<?>, List<?>> listFunction,
                              Long totalCount, DownloadDto downloadDto, List<String> ignoreParamList, Object... objects) {
        String uuid = downloadDto.getUuid();
        String jsonStr = toJsonStr(objects);

        String downloadKey = "exportInBatches_" + uuid;
        Boolean ifAbsent = redisUtil.setIfAbsent(downloadKey, 0, 60 * 60 * 5);
        if (!ifAbsent) {
            log.error("文件下载 {} 导出任务uuid:{}  导出任务处理中 参数信息：{}", fileName, uuid, jsonStr);
            return R.failed("文件下载 导出任务处理中:" + uuid, downloadDto);
        }

        String startTime = DateUtil.now();

        String fullFilePath = filePath + fileName;
        String fullRelativePath = baseUrl + relativePath + fileName;
        File dir = new File(filePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        var downloadList = downloadUtil.getDownloadList(downloadDto.getZhangHu(), downloadDto.getModel());
        if (ObjectUtils.isNotEmpty(downloadList)) {
            long whetherExportIsComplete = downloadList.stream().filter(dto -> dto.getUuid().equals(uuid) && dto.getExportStatus().equals(3)).count();
            if (whetherExportIsComplete > 0) {
                log.error("文件下载 {} 导出任务uuid:{}  非正常逻辑变为导出完成 参数信息：{}", fileName, uuid, jsonStr);
                redisUtil.delKeys("Download_" + downloadDto.getZhangHu(), downloadKey);
                return R.success(fullRelativePath);
            }
        }


        log.info("文件下载 {} 导出任务uuid:{} 数据导出开始...开始时间:{}  参数信息：{}", fileName, uuid, startTime, jsonStr);

        var readOnlyDef = new DefaultTransactionDefinition();
        var status = transactionManager.getTransaction(readOnlyDef);

        try (var excelWriter = EasyExcel.write(fullFilePath, modelClass).excludeColumnFieldNames(ignoreParamList).build()) {
            // 单次查询的数据量
            int queryCount = QUERY_COUNT;
            // 批次总数
            long queryCountPage = totalCount % queryCount == 0 ? (totalCount / queryCount) : (totalCount / queryCount + 1);
            // 单个sheet保存的数据量
            int perSheetCount = PER_SHEET_COUNT;
            // sheet总数
            long sheetCount = totalCount % perSheetCount == 0 ? (totalCount / perSheetCount) : (totalCount / perSheetCount + 1);

            downloadDto.setExportStatus(2);
            downloadDto.setTotal(totalCount);
            downloadUtil.updateDownloadList(downloadDto);

            log.info("文件下载 {} 导出任务uuid:{} 总数据量:{},共计{}个sheet,{}个批次", fileName, uuid, totalCount, sheetCount, queryCountPage);

            int currentSheetSize = 0;
            // 初始化sheetIndex为0
            int sheetIndex = 0;

            // 创建第一个sheet页
            var sheet = EasyExcel.writerSheet(sheetIndex, "数据页-" + sheetIndex).build();

            PageUtils<?> pageUtils = new PageUtils<>();
            for (int start = 1; start <= queryCountPage; start++) {
                if (!redisUtil.hasKey(downloadKey)) {
                    log.info("文件下载 {} 导出任务uuid:{} 终止下载第{}个查询批次数据...终止时间:{}", fileName, uuid, start, DateUtil.now());
                    log.error("文件下载 {} 导出任务uuid:{} 终止下载第{}个查询批次数据...终止时间:{}", fileName, uuid, start, DateUtil.now());
                    throw new RuntimeException("终止下载");
                }

                String startTime1 = DateUtil.now();
                log.info("文件下载 {} 导出任务uuid:{} 开始读取第{}个查询批次数据...开始时间:{}", fileName, uuid, start, startTime1);
                PageHelper.startPage(start, queryCount, false);
                pageUtils.setCurrentPage(start);
                pageUtils.setPageSize(queryCount);
                List<?> dataList;
                if (listFunction != null) {
                    dataList = listFunction.apply(pageUtils);
                } else if (listSupplier != null) {
                    dataList = listSupplier.get();
                } else {
                    throw new RuntimeException("listFunction和listSupplier不能同时为空");
                }

                PageHelper.clearPage();
                int size = dataList.size();
                log.info("文件下载 {} 导出任务uuid:{} 开始读取第{}个查询批次数据...开始时间：{} 结束时间:{} 数据条数：{}", fileName, uuid, start, startTime1, DateUtil.now(), size);

                currentSheetSize += size;

                String startTime2 = DateUtil.now();
                log.info("文件下载 {} 导出任务uuid:{} 开始写入第{}个sheet,{}批次数据...开始时间:{} 数据条数：{}", fileName, uuid, sheetIndex, start, startTime2, size);
                excelWriter.write(dataList, sheet);
                log.info("文件下载 {} 导出任务uuid:{} 开始写入第{}个sheet,{}批次数据...开始时间：{} 结束时间:{} 数据条数：{}", fileName, uuid, sheetIndex, start, startTime2, DateUtil.now(), size);
                // 清空当前累积的数据列表
                dataList.clear();

                downloadDto.setProgress(Tools.getDoublePrecision(start * 1.0 / queryCountPage * 100.0, 2));
                downloadUtil.updateDownloadList(downloadDto);

                if (currentSheetSize >= perSheetCount || ((long) start * queryCount) > totalCount) {
                    // 当前sheet已满或已经是最后一批数据
                    currentSheetSize = 0;
                    sheetIndex++;
                    // 创建新的sheet页
                    sheet = EasyExcel.writerSheet(sheetIndex, "数据页-" + sheetIndex).build();
                }
            }
            excelWriter.finish();
            transactionManager.commit(status);
            downloadDto.setProgress(100);
            downloadDto.setExportStatus(3);
            downloadDto.setDownloadUrl(fullRelativePath);
            downloadDto.setDescription("导出完成，请尽快下载，服务端将保留24小时");
        } catch (Exception e) {
            log.error("文件下载 {} 导出任务uuid:{} 数据导出异常....开始时间：{} 结束时间:{}  参数信息：{}", fileName, uuid, startTime, DateUtil.now(), jsonStr, e);
            transactionManager.rollback(status);
            downloadDto.setDescription("下载失败，请重新下载：" + (StrUtil.length(e.getMessage()) < 100 ? e.getMessage() : "未知错误"));
            downloadDto.setExportStatus(4);
        } finally {
            redisUtil.delKeys("Download_" + downloadDto.getZhangHu(), downloadKey);
            downloadDto.setEndDate(DateUtil.date());
            downloadUtil.updateDownloadList(downloadDto);
        }


        log.info("文件下载 {} 导出任务uuid:{} 数据导出完成....开始时间：{} 结束时间:{}  参数信息：{}", fileName, uuid, startTime, DateUtil.now(), jsonStr);
        return R.success(fullRelativePath);
    }


    /**
     * 导出
     * <p>
     * 调用示例：
     * <pre>
     *   public R download(QueryDto dto, DownloadDto downloadDto) {
     *
     *         String fileName = getFileNameDate("订单_") + ".xlsx";
     *
     *         return easyExcelUtils.exportInBatches(fileName, FilePath.DOWN_ORDER, FilePath.DOWN_ORDER_XD,
     *                 OrderBean.class,
     *                 () -> orderMapper.selectAll(dto),
     *                 downloadDto, dto);
     *     }
     *
     * @param fileName     文件名
     * @param filePath     文件路径
     * @param relativePath 相对路径
     * @param modelClass   模型类
     * @param listSupplier 数据订处理器
     * @param downloadDto  下载 DTO
     * @param ignoreParamList 忽略的字段
     * @param objects      参数信息
     * @return {@link R }
     * @date 2024/09/06
     * @author SongRenShuo
     */
    public R exportInBatches(String fileName, String filePath, String relativePath,
                             Class<?> modelClass, Supplier<List<?>> listSupplier,
                             DownloadDto downloadDto, List<String> ignoreParamList, Object... objects) {
        String uuid = downloadDto.getUuid();
        String jsonStr = toJsonStr(objects);

        String downloadKey = "exportInBatches_" + uuid;
        Boolean ifAbsent = redisUtil.setIfAbsent(downloadKey, 0, 60 * 60 * 5);
        if (!ifAbsent) {
            log.error("文件下载 {} 导出任务uuid:{}  导出任务处理中 参数信息：{}", fileName, uuid, jsonStr);
            return R.failed("文件下载 导出任务处理中:" + uuid, downloadDto);
        }

        String startTime = DateUtil.now();

        String fullFilePath = filePath + fileName;
        String fullRelativePath = baseUrl + relativePath + fileName;
        File dir = new File(filePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        log.info("文件下载 {} 导出任务uuid:{} 数据导出开始...开始时间:{}  参数信息：{}", fileName, uuid, startTime, jsonStr);

        var readOnlyDef = new DefaultTransactionDefinition();
        var status = transactionManager.getTransaction(readOnlyDef);

        try (var excelWriter = EasyExcel.write(fullFilePath, modelClass).excludeColumnFieldNames(ignoreParamList).build()) {

            downloadDto.setExportStatus(2);
            downloadUtil.updateDownloadList(downloadDto);

            log.info("文件下载 {} 导出任务uuid:{} 总数据量:{},共计{}个sheet,{}个批次", fileName, uuid, 1, 1, 1);

            // 初始化sheetIndex为0
            int sheetIndex = 0;

            // 创建第一个sheet页
            var sheet = EasyExcel.writerSheet(sheetIndex, "数据页-" + sheetIndex).build();
            String startTime1 = DateUtil.now();
            log.info("文件下载 {} 导出任务uuid:{} 开始读取第{}个查询批次数据...开始时间:{}", fileName, uuid, 1, startTime1);
            var dataList = listSupplier.get();
            int size = dataList.size();
            log.info("文件下载 {} 导出任务uuid:{} 开始读取第{}个查询批次数据...开始时间:{} 结束时间:{} 数据条数：{}", fileName, uuid, 1, startTime1, DateUtil.now(), size);
            String startTime2 = DateUtil.now();
            log.info("文件下载 {} 导出任务uuid:{} 开始写入第{}个sheet,{}批次数据...开始时间:{} 数据条数：{}", fileName, uuid, sheetIndex, 1, startTime2, size);
            excelWriter.write(dataList, sheet);
            log.info("文件下载 {} 导出任务uuid:{} 开始写入第{}个sheet,{}批次数据...开始时间:{} 结束时间:{} 数据条数：{}", fileName, uuid, sheetIndex, 1, startTime2, DateUtil.now(), size);
            // 清空当前累积的数据列表
            dataList.clear();

            excelWriter.finish();
            transactionManager.commit(status);
            downloadDto.setProgress(100);
            downloadDto.setExportStatus(3);
            downloadDto.setTotal(size);
            downloadDto.setDownloadUrl(fullRelativePath);
            downloadDto.setDescription("导出完成，请尽快下载，服务端将保留24小时");
        } catch (Exception e) {
            log.error("文件下载 {} 导出任务uuid:{} 数据导出异常....开始时间：{} 结束时间:{}  参数信息：{}", fileName, uuid, startTime, DateUtil.now(), jsonStr, e);
            transactionManager.rollback(status);
            downloadDto.setDescription("下载失败，请重新下载");
            downloadDto.setExportStatus(4);
        } finally {
            redisUtil.delKeys("Download_" + downloadDto.getZhangHu(), downloadKey);
            downloadDto.setEndDate(DateUtil.date());
            downloadUtil.updateDownloadList(downloadDto);
        }

        log.info("文件下载 {} 导出任务uuid:{} 数据导出完成....开始时间：{} 结束时间:{}  参数信息：{}", fileName, uuid, startTime, DateUtil.now(), jsonStr);
        return R.success(fullRelativePath);
    }


    /**
     * 文件上传预处理
     *
     * @param filePath      文件路径
     * @param absolutePath  结果文件的绝对路径
     * @param relativePaths 结果文件的相对路径
     * @param entityClass   实体类
     * @param readListener 监听器
     * @param downloadDto   导出DTO
     * @param batchCount   存储结果的列表
     * @date 2024/12/01
     * @author SongRenShuo
     */
    public void fileUploadPreprocessing(String filePath, String absolutePath, String relativePaths, Class<?>
            entityClass, ReadListener<?> readListener, DownloadDto downloadDto, BatchCount batchCount) {

        downloadDto.setExportStatus(2);
        downloadDto.setDescription("数据处理中，请耐心等待");
        downloadUtil.updateDownloadList(downloadDto);
        String jsonStr;
        try {

            EasyExcel.read(filePath, entityClass, readListener).sheet().doRead();


            jsonStr = toJsonStr(batchCount);
            log.info("{}最终执行结果：{}", downloadDto.getFileName(), jsonStr);

            String fileName = FilePath.getFileNameDate(downloadDto.getFileName() + "_") + ".xlsx";

            String fullFilePath = absolutePath + fileName;
            String fullRelativePath = baseUrl + relativePaths + fileName;
            File dir = new File(absolutePath);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            EasyExcel.write(fullFilePath, BatchCount.BatchCountDto.class)
                    .sheet(fileName)
                    .doWrite(batchCount.getErrorMsg());

            downloadDto.setTotal(batchCount.getTotalCount());
            downloadDto.setProgress(100);
            downloadDto.setExportStatus(3);
            downloadDto.setDownloadUrl(fullRelativePath);
            downloadDto.setDescription("总订单数：" + batchCount.getTotalCount() + "  成功订单数：" + batchCount.getSuccessCount() + "  失败订单数：" + batchCount.getErrorCount());
            if (batchCount.getErrorCount() < 100) {
                downloadDto.add("errorMsg", batchCount.getErrorMsg());
            }
        } catch (Exception e) {
            log.error("文件上传 {} 数据导出异常.... 异常信息：{}", DateUtil.now(), e.getMessage(), e);
            String description = StrUtil.format("{}导出失败:{}，请调整后重试", downloadDto.getFileName(), StrUtil.length(e.getMessage()) < 100 ? e.getMessage() : "未知错误");
            downloadDto.setDescription(description);
            downloadDto.setExportStatus(4);
        } finally {
            redisUtil.del("Upload_" + downloadDto.getZhangHu());
            downloadDto.setEndDate(DateUtil.date());
            downloadUtil.updateDownloadList(downloadDto);
        }
    }

    /**
     * 文件上传预处理 - 调用示例
     *
     * @param dto DTO
     * @param pageUtils 页面实用程序
     * @date 2025/04/06
     * @return {@link List }<{@link OrderBean }>
     * @author SongRenShuo
     *//*
      /**
     * 文件上传 - 批量导单
     *
     * @param file 文件
     * @date 2025/04/04
     * @author SongRenShuo
     */
/*    @PostMapping("/uploadBulkOrder")
    public R<String> uploadBulkOrder(MultipartFile file) throws IOException {
        if (ObjUtil.isNull(file) || file.isEmpty()) {
            return R.failed("文件不能为空");
        }
        String zhangHu = Tools.getZhangHu(null, request);

        String key = "Upload_" + zhangHu;

        Boolean isExist = redisUtil.setIfAbsent(key, 0, 60 * 30);
        if (!isExist) {
            log.error("文件上传 - 批量导单:频率过高,账户：{}", zhangHu);
            return R.failed("当前存在未完成的导入任务，请稍后再试");
        }
        DownloadDto downloadDto = DownloadDto.builder()
                .zhangHu(zhangHu)
                .fileName("批量导单结果")
                .model(4).build();

        distributorAssignment(downloadDto);
        downloadUtil.updateDownloadList(downloadDto);
        File dest = Tools.handleFileUpload(file, GongXiaoFilePath.IMPORT_BULK_ORDER);
        asyncExecutor.execute(() -> {
            try {
                BatchCount batchCount = new BatchCount();
                ExcelOrderImportListener readListener = new ExcelOrderImportListener(tDingdanxiangqingService, downloadUtil, downloadDto, batchCount);
                easyExcelUtils.fileUploadPreprocessing(dest.getCanonicalPath(), GongXiaoFilePath.IMPORT_BULK_ORDER, GongXiaoFilePath.IMPORT_BULK_ORDER_RELATIVELY, SaveOrderImport.class, readListener, downloadDto, batchCount);
            } catch (Exception e) {
                log.error("批量导单 文件{}上传异常:{}", dest.getName(), e.getMessage(), e);
                redisUtil.del(key);
                downloadDto.setDescription("批量建单 读取文件失败:"
                        + (StrUtil.length(e.getMessage()) < 100 ? e.getMessage() : "未知错误")
                        + "，请重试，多次失败请联系管理员");
                downloadDto.setExportStatus(4);
                downloadUtil.updateDownloadList(downloadDto);
                throw new RuntimeException(e.getMessage());
            }
        });

        return R.success("批量导单任务创建成功,导入结果请在当前页面点击【刷新】查看");
    }*/


    /**
     * 分批导出 - 年份分表 - downOrder方法示例
     */
/*
    public List<OrderBean> downOrder(QueryDto dto, PageUtils<?> pageUtils) {
        int pageSize = pageUtils.getPageSize();
        List<OrderBean> orderBeans;
        String yearName1 = dto.getStartTime().toString().split("-")[0];
        String yearName2 = dto.getEndTime().toString().split("-")[0];
        orderBeans = orderMapper.downOrder(dto);
        if (!yearName1.equals(yearName2) && pageSize != orderBeans.size()) {
            param.put("yearName", yearName2);
            PageHelper.startPage(pageUtils.getCurrentPage(), pageSize, false);
            List<OrderBean> orderBeans02 = orderMapper.downOrder(dto);
            PageHelper.clearPage();
            orderBeans.addAll(orderBeans02);
        }
        return orderBeans;
    }
*/

    /**
     * 重置单元格最大文本长度
     * <p>
     *  解决导出时因字段数据过长报错：The maximum length of cell contents (text) is 32767 characters
     *
     *
     * @date 2025/04/21
     * @author SongRenShuo
     *
     */
    public static void resetCellMaxTextLength() {
        try {
            SpreadsheetVersion excel2007 = SpreadsheetVersion.EXCEL2007;
            if (Integer.MAX_VALUE != excel2007.getMaxTextLength()) {
                Field field;

                // SpreadsheetVersion.EXCEL2007的_maxTextLength变量
                field = excel2007.getClass().getDeclaredField("_maxTextLength");
                // 关闭反射机制的安全检查，可以提高性能
                field.setAccessible(true);
                // 重新设置这个变量属性值
                field.set(excel2007, Integer.MAX_VALUE);
                log.info("重置SpreadsheetVersion.EXCEL2007的_maxTextLength变量属性值成功");
            }
        } catch (Exception e) {
            log.error("重置SpreadsheetVersion.EXCEL2007的_maxTextLength变量属性值失败");
        }
    }


}


